package com.xiaoyu.tio.redis.core;

import java.io.Closeable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tio.client.ClientChannelContext;
import org.tio.client.ClientGroupContext;
import org.tio.client.ReconnConf;
import org.tio.client.TioClient;
import org.tio.core.Node;
import org.tio.core.Tio;

import com.xiaoyu.tio.redis.config.TedisConfig;
import com.xiaoyu.tio.redis.core.client.TedisStandaloneClient;
import com.xiaoyu.tio.redis.core.handler.RedisHandler;
import com.xiaoyu.tio.redis.core.pool.TedisPool;
import com.xiaoyu.tio.redis.core.protocol.StringCodec;
import com.xiaoyu.tio.redis.core.protocol.TedisCodec;

public class Tedis<K ,V> implements Closeable {

	private final static Logger logger = LoggerFactory.getLogger(Tedis.class);

	private TedisConfig redisConfig;

	private ClientChannelContext clientChannelContext;

	private RedisHandler redisHandler;

	private TioClient aioClient;
	
	private TedisPool tedisPool;
	
	private TedisStandaloneClient<K ,V> tedisClient;
	
	@SuppressWarnings("rawtypes")
	private TedisCodec tedisCodec = new StringCodec();
	
	private final static String OK = "OK";

	public Tedis(TedisConfig config) {
		this.redisConfig = config;
		this.tedisPool = new TedisPool(config);
	}
	
	public Tedis(TedisConfig config, TedisCodec<?, ?, ?> tedisCodec) {
		this.redisConfig = config;
		this.tedisPool = new TedisPool(config);
		this.tedisCodec = tedisCodec;
	}

	public void connect() throws Exception {
		if (clientChannelContext != null) {
			logger.error("请勿重复调用connect方法！！");
		}
		Node serverNode = new Node(redisConfig.getHost(), redisConfig.getPort());
		redisHandler = new RedisHandler();
		ReconnConf reconnConf = new ReconnConf();
		ClientGroupContext groupContext = new ClientGroupContext(redisHandler, null, reconnConf);
		aioClient = new TioClient(groupContext);
		clientChannelContext = aioClient.connect(serverNode);
		if (redisConfig.getPassword() != null && !redisConfig.getPassword().equals("")) {
			Boolean f = auth(redisConfig.getPassword());
			if (!f) {
				throw new IllegalArgumentException("redis密碼錯誤！！");
			}
		}
		Boolean f = select(redisConfig.getDatabase());
		if (!f) {
			throw new IllegalArgumentException("选择redis数据库" + redisConfig.getDatabase() + "失败！！");
		}
		logger.debug("Tedis连接成功！！");
	}


	public Boolean exists(Object key) throws TimeoutException {
		MessageResponse m = sendCommand(RedisCmd.EXISTS, getRawKey(key));
		String a = new String(m.getData().get(0));
		if ("1".equals(a)) {
			return true;
		}
		return false;
	}

	public TedisCodec getTedisCodec() {
		return tedisCodec;
	}

	public void setTedisCodec(TedisCodec tedisCodec) {
		this.tedisCodec = tedisCodec;
	}

	public Boolean auth(String password) throws TimeoutException {
		MessageResponse m = sendCommand(RedisCmd.AUTH, password.getBytes());
		String a = new String(m.getData().get(0));
		if (OK.equals(a)) {
			return true;
		}
		return false;
	}

	public Boolean select(Integer db) throws TimeoutException {
		MessageResponse m = sendCommand(RedisCmd.SELECT, db.toString().getBytes());
		String a = new String(m.getData().get(0));
		if (OK.equals(a)) {
			return true;
		}
		return false;
	}

	@SuppressWarnings("unchecked")
	private byte[] getRawKey(Object key) {
		if (key instanceof byte[]) {
			return (byte[]) key;
		}
		return tedisCodec.encoderKey(key);
	}

	public MessageResponse sendCommand(RedisCmd cmd, byte[]... parms) throws TimeoutException {
		return this.sendCommand(cmd.name(), parms);
	}

	public MessageResponse sendCommand(String cmd, byte[]... parms) throws TimeoutException {
		if (clientChannelContext == null) {
			logger.error("请先调用connect方法！！");
			throw new IllegalArgumentException("请先调用connect方法！！");
		}
		RedisPacket packet = new RedisPacket();
		List<byte[]> arrayParms = new ArrayList<>();
		packet.setCmd(cmd);
		for (byte[] o : parms) {
			arrayParms.add(o);
		}
		packet.setParms(arrayParms);
		Tio.send(clientChannelContext, packet);
		MessageResponse response = null;
		try {
			response = packet.getFuture().get(1000, TimeUnit.SECONDS);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		} catch (TimeoutException e) {
			throw e;
		}
		return response;
	}

	@Override
	public void close() {
		aioClient.stop();
	}
	
	public Boolean isActive() {
		if(!clientChannelContext.isClosed) {
			return true;
		}
		return false;
	}
	
	public TedisPool getTedisPool() {
		return tedisPool;
	}

	public TedisStandaloneClient<K, V> getTedisClient() {
		if(tedisClient == null) {
			tedisClient = new TedisStandaloneClient<>(this);
		}
		return tedisClient;
	}

}
